import os
import json
from uuid import uuid4

from PIL import Image as PILImage
import requests
import sys
from io import BytesIO
from typing import List, Optional, Dict
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.dml.color import RGBColor
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.agents import AgentExecutor, create_openai_functions_agent
from tavily import TavilyClient
from langchain.tools import Tool
from dotenv import load_dotenv
from openai import OpenAI
from http import HTTPStatus
from urllib.parse import urlparse, unquote
from pathlib import PurePosixPath
import requests
from dashscope import ImageSynthesis
import os
from core.config import settings

from core.llm import get_default_llm
from datetime import datetime

# 加载环境变量
load_dotenv()


# 配置日志输出
def log_step(message: str):
    """打印生成步骤"""
    print(f"\033[94m[生成进度]\033[0m {message}", file=sys.stderr)


class PPTGenerator:
    """PPT生成器"""

    def __init__(self):
        """初始化PPT生成器"""
        # 初始化语言模型
        self.llm = get_default_llm()

        # 创建提示模板
        self.prompt = ChatPromptTemplate.from_messages([
            ("system", """你是一个专业的PPT设计师，可以帮助用户生成高质量的PPT。你必须严格按照以下步骤操作：

2. 从用户输入中提取两个关键信息：
   - 内容：通常在“内容：”之后 ，如 内容：2025年的创业机遇。
   - 页数：通常以"需要X页"的形式出现，如"需要3页"表示pages=3

3. 调用generate_ppt工具时，必须同时提供content和pages两个参数。

4. 重要提醒：
   - 内容可能会很长
   - 你必须从用户输入中找到"需要X页"，并将X作为pages参数的值
   - 你必须同时提供content和pages两个参数
   - 你绝对不能省略任何参数
   - 你绝对不能使用默认的页数

5. 在调用工具之前，请先确认：
   - 你是否已经从用户输入中找到了页数？
   - 你是否同时准备了两个参数？
   如果有任何一个问题的答案是"否"，请不要调用工具。"""),
            ("human", "{input}"),
            MessagesPlaceholder(variable_name="agent_scratchpad")
        ])

        # 创建代理
        self.agent = create_openai_functions_agent(
            llm=self.llm,
            tools=[],
            prompt=self.prompt
        )

        self.agent_executor = AgentExecutor(
            agent=self.agent,
            tools=[],
            verbose=True,
            handle_parsing_errors=True
        )

    def _clean_json_string(self, text: str) -> str:
        """清理JSON字符串"""
        # 找到第一个完整的JSON对象
        start = text.find('{')
        end = -1
        brace_count = 0
        for i in range(start, len(text)):
            if text[i] == '{':
                brace_count += 1
            elif text[i] == '}':
                brace_count -= 1
                if brace_count == 0:
                    end = i + 1
                    break

        if start == -1 or end == -1:
            raise ValueError("未找到有效的JSON内容")

        json_str = text[start:end]

        # 移除代码块标记和注释
        json_str = json_str.replace('```json', '')
        json_str = json_str.replace('```', '')
        json_str = json_str.replace('/*', '')
        json_str = json_str.replace('*/', '')

        # 添加缺失的逗号
        lines = json_str.split('\n')
        processed_lines = []
        for i, line in enumerate(lines):
            line = line.strip()
            if not line:
                continue

            # 如果不是最后一行且当前行以引号结尾且下一行不是结束标记
            if i < len(lines) - 1 and line.rstrip().endswith('"') and not lines[i + 1].strip().startswith('}') and not \
            lines[i + 1].strip().startswith(']'):
                line += ','

            # 如果不是最后一行且当前行以大括号或方括号结尾且下一行不是结束标记
            if i < len(lines) - 1 and (line.rstrip().endswith('}') or line.rstrip().endswith(']')) and not lines[
                i + 1].strip().startswith('}') and not lines[i + 1].strip().startswith(']'):
                line += ','

            processed_lines.append(line)

        json_str = '\n'.join(processed_lines)

        # 确保所有属性名都有双引号
        for prop in ['title', 'slides', 'content', 'image_prompt', 'search_query']:
            json_str = json_str.replace(f'{prop}:', f'"{prop}":')
            json_str = json_str.replace(f'"{prop}" :', f'"{prop}":')

        # 确保所有字符串值都有双引号
        import re
        json_str = re.sub(r':\s*([^",\{\[\]\}\s][^",\{\[\]\}\s]*)', r': "\1"', json_str)

        return json_str

    def generate_outline(self, content: str, pages: int = 3) -> dict:
        """生成PPT大纲"""
        log_step(f"开始为内容「{content}」生成{pages}页PPT大纲...")

        try:
            # 生成提示
            prompt = f"""请为内容「{content}」生成一个完整的PPT大纲，要求生成{pages}页。

请严格按照以下JSON格式返回，注意：
1. 所有属性和数组项之间必须用英文逗号分隔
2. 所有字符串必须用英文双引号包裹
3. 不要在JSON中添加任何注释
4. 确保生成的是有效的JSON格式

{{
    "title": "PPT标题",
    "slides": [
        {{
            "title": "第一页标题",
            "content": [
                "要点1",
                "要点2",
                "要点3"
            ],
            "image_prompt": "",
            "detail": "对content的详细解释"
        }}
    ]
}}

内容要求：
1. 必须生成{pages}页幻灯片，内容要丰富全面
2. 每页幻灯片需包含：
   - 标题：清晰的中文标题（不超过15字）
   - 内容：3-5个简洁的要点，形式多样化（可使用要点、表格、流程图、对比等）
   - 详细内容：详细描述内容中的要点，但字数不要过多
3. 结构要完整，包含：
   - 开篇：引言和背景介绍
   - 主体：详细分析和论述
   - 结尾：总结和展望
4. 内容要专业，数据要准确
5. 表达要简洁，逻辑要清晰
6. 专注于内容布局和形式美观化
7. 适合教学使用，便于学生理解"""

            response = self.agent_executor.invoke({
                "input": prompt
            })

            log_step("大纲生成完成，开始解析内容...")
            
            # 尝试解析JSON
            json_str = self._clean_json_string(response["output"])
            outline = json.loads(json_str)
            
            # 验证大纲结构
            if not outline.get('title'):
                outline['title'] = "专业教学课件"
            
            if not outline.get('slides') or len(outline['slides']) == 0:
                # 如果AI生成失败，使用默认大纲
                outline['slides'] = [
                    {
                        "title": "课程概述",
                        "content": ["课程介绍", "学习目标", "主要内容"],
                        "image_prompt": "",
                        "detail": "介绍课程的基本信息和学习目标"
                    },
                    {
                        "title": "核心知识点",
                        "content": ["重点概念", "关键原理", "重要方法"],
                        "image_prompt": "",
                        "detail": "详细讲解课程的核心知识点"
                    },
                    {
                        "title": "实践应用",
                        "content": ["操作步骤", "注意事项", "案例分析"],
                        "image_prompt": "",
                        "detail": "展示实际应用和操作过程"
                    }
                ]
            
            # 确保幻灯片数量符合要求
            while len(outline['slides']) < pages:
                outline['slides'].append({
                    "title": f"补充内容 {len(outline['slides']) + 1}",
                    "content": ["补充要点1", "补充要点2", "补充要点3"],
                    "image_prompt": "",
                    "detail": "补充内容说明"
                })
            
            # 限制幻灯片数量
            outline['slides'] = outline['slides'][:pages]
            
            return outline
            
        except Exception as e:
            print(f"\033[91m[错误]\033[0m PPT大纲生成失败: {str(e)}", file=sys.stderr)
            
            # 返回默认大纲
            default_outline = {
                "title": "专业教学课件",
                "slides": [
                    {
                        "title": "课程概述",
                        "content": ["课程介绍", "学习目标", "主要内容"],
                        "image_prompt": "",
                        "detail": "介绍课程的基本信息和学习目标"
                    },
                    {
                        "title": "核心知识点",
                        "content": ["重点概念", "关键原理", "重要方法"],
                        "image_prompt": "",
                        "detail": "详细讲解课程的核心知识点"
                    },
                    {
                        "title": "实践应用",
                        "content": ["操作步骤", "注意事项", "案例分析"],
                        "image_prompt": "",
                        "detail": "展示实际应用和操作过程"
                    },
                    {
                        "title": "学习总结",
                        "content": ["知识回顾", "应用建议", "拓展方向"],
                        "image_prompt": "",
                        "detail": "总结课程内容，提供学习建议"
                    }
                ]
            }
            
            # 调整默认大纲的页数
            default_outline['slides'] = default_outline['slides'][:pages]
            
            return default_outline

    def search_info(self, query: str) -> str:
        """搜索补充信息"""
        log_step(f"正在搜索关键信息：{query}")

        response = self.agent_executor.invoke({
            "input": f"""搜索'{query}'相关的最新数据和信息，生成5个重要观点。要求：

1. 每个观点必须包含具体数字或百分比
2. 每个观点字数在10-50字之间
3. 观点要突出增长趋势和发展前景
4. 内容要涵盖市场规模、增长率、未来预测等方面
5. 按重要性排序，最重要的观点放在前面
6. 每个观点单独成段，使用从1开始的数字序号
7. 语言要简洁专业，数据要准确可信

注意：
- 必须使用从1到5的数字序号，不要使用字母序号
- 每个观点都是独立的，不需要子分类
- 每行只使用一个序号，格式为"1. "，"2. "等

示例格式：

"""
        })

        return response["output"]
    def generate_image(self, prompt: str) -> Optional[BytesIO]:


        print('----sync call, please wait a moment----')
        rsp = ImageSynthesis.call(api_key=settings.LLM_API_KEY,
                          model="wanx2.1-t2i-turbo",
                          prompt=prompt,
                          n=1,
                          size='1024*1024')
        print('response: %s' % rsp)
        if rsp.status_code == HTTPStatus.OK:
        # 在当前目录下保存图片
            for result in rsp.output.results:
                response = requests.get(result.url)
                return BytesIO(response.content)
        else:
            print('sync_call Failed, status_code: %s, code: %s, message: %s' %
              (rsp.status_code, rsp.code, rsp.message))

    def create_ppt(self, outline: dict, output_path: str):
        """创建PPT文件"""
        log_step("开始创建PPT文件...")

        prs = Presentation()

        # 设置16:9比例
        prs.slide_width = Inches(16)
        prs.slide_height = Inches(9)

        # 创建封面
        log_step("正在创建封面...")
        cover_slide = prs.slides.add_slide(prs.slide_layouts[6])  # 使用空白布局

        # 添加标题
        title_left = Inches(0.5)
        title_top = Inches(1.5)
        title_width = Inches(15)
        title_height = Inches(2.0)

        title_box = cover_slide.shapes.add_textbox(
            title_left, title_top, title_width, title_height
        )

        title_frame = title_box.text_frame
        title_frame.text = outline["title"]
        title_font = title_frame.paragraphs[0].font
        title_font.size = Pt(48)
        title_font.bold = True
        title_font.name = "微软雅黑"
        title_font.color.rgb = RGBColor(31, 73, 125)  # 深蓝色
        title_frame.paragraphs[0].alignment = PP_ALIGN.CENTER

        # 添加副标题
        subtitle_left = Inches(0.5)
        subtitle_top = Inches(4.0)
        subtitle_width = Inches(15)
        subtitle_height = Inches(1.0)

        subtitle_box = cover_slide.shapes.add_textbox(
            subtitle_left, subtitle_top, subtitle_width, subtitle_height
        )

        subtitle_frame = subtitle_box.text_frame
        subtitle_frame.text = "专业教学课件"
        subtitle_font = subtitle_frame.paragraphs[0].font
        subtitle_font.size = Pt(28)
        subtitle_font.bold = False
        subtitle_font.name = "微软雅黑"
        subtitle_font.color.rgb = RGBColor(89, 89, 89)  # 灰色
        subtitle_frame.paragraphs[0].alignment = PP_ALIGN.CENTER

        # 添加生成时间
        time_left = Inches(0.5)
        time_top = Inches(6.5)
        time_width = Inches(15)
        time_height = Inches(0.8)

        time_box = cover_slide.shapes.add_textbox(
            time_left, time_top, time_width, time_height
        )

        time_frame = time_box.text_frame
        time_frame.text = f"生成时间：{datetime.now().strftime('%Y年%m月%d日')}"
        time_font = time_frame.paragraphs[0].font
        time_font.size = Pt(18)
        time_font.bold = False
        time_font.name = "微软雅黑"
        time_font.color.rgb = RGBColor(128, 128, 128)  # 浅灰色
        time_frame.paragraphs[0].alignment = PP_ALIGN.CENTER

        # 创建内容页
        log_step("开始创建内容页...")
        for i, slide_data in enumerate(outline["slides"], 1):
            log_step(f"正在创建第 {i} 页：{slide_data['title']}")

            # 使用空白布局
            slide = prs.slides.add_slide(prs.slide_layouts[6])

            # 添加标题
            title_left = Inches(0.5)
            title_top = Inches(0.5)
            title_width = Inches(15)
            title_height = Inches(1.2)

            title_box = slide.shapes.add_textbox(
                title_left, title_top, title_width, title_height
            )

            title_frame = title_box.text_frame
            title_frame.text = slide_data["title"]
            title_font = title_frame.paragraphs[0].font
            title_font.size = Pt(36)
            title_font.bold = True
            title_font.name = "微软雅黑"
            title_font.color.rgb = RGBColor(31, 73, 125)  # 深蓝色
            title_frame.paragraphs[0].alignment = PP_ALIGN.CENTER

            # 创建主要内容区域
            content_left = Inches(1.0)
            content_top = Inches(2.0)
            content_width = Inches(14.0)
            content_height = Inches(5.5)

            content_box = slide.shapes.add_textbox(
                content_left, content_top, content_width, content_height
            )
            tf = content_box.text_frame
            tf.word_wrap = True
            tf.vertical_anchor = MSO_ANCHOR.TOP

            # 添加主要内容
            for idx, point in enumerate(slide_data["content"], 1):
                p = tf.add_paragraph()
                p.text = f"• {point}"  # 使用项目符号
                p.font.size = Pt(24)
                p.font.name = "微软雅黑"
                p.font.color.rgb = RGBColor(0, 0, 0)  # 黑色
                p.space_after = Pt(20)
                p.space_before = Pt(12)
                p.level = 0  # 一级缩进

            # 添加详细说明
            if "detail" in slide_data and slide_data["detail"]:
                detail_result = slide_data["detail"]
                # 添加分隔线
                p = tf.add_paragraph()
                p.text = "─" * 60
                p.font.size = Pt(16)
                p.font.name = "微软雅黑"
                p.font.color.rgb = RGBColor(200, 200, 200)  # 浅灰色
                p.space_after = Pt(20)
                p.alignment = PP_ALIGN.CENTER
                
                # 添加详细说明标题
                p = tf.add_paragraph()
                p.text = "详细说明："
                p.font.bold = True
                p.font.size = Pt(22)
                p.font.name = "微软雅黑"
                p.font.color.rgb = RGBColor(68, 114, 196)  # 深蓝色
                p.space_after = Pt(16)
                
                # 添加详细内容
                p = tf.add_paragraph()
                p.text = detail_result
                p.font.size = Pt(20)
                p.font.name = "微软雅黑"
                p.font.color.rgb = RGBColor(89, 89, 89)  # 灰色
                p.space_after = Pt(12)

        # 保存PPT
        log_step("正在保存PPT文件...")
        prs.save(output_path)
        log_step(f"PPT生成完成！文件保存在：{output_path}")

    def generate(self, content: str, pages: int = 3) -> str:
        """生成完整的PPT"""
        try:
            # 创建输出目录
            output_dir = "./ppt"
            os.makedirs(output_dir, exist_ok=True)

            # 验证输入参数
            if not content or content.strip() == "":
                raise ValueError("内容不能为空")
            
            pages = max(1, min(pages, 20))  # 确保页数在1-20之间
            
            # 生成大纲
            outline = self.generate_outline(content, pages)
            
            # 验证大纲
            if not outline or not outline.get('title') or not outline.get('slides'):
                raise ValueError("PPT大纲生成失败")
            
            # 生成唯一文件名
            uuid = str(uuid4())
            output_path = os.path.join(output_dir, f"{uuid}.pptx")

            # 创建PPT
            self.create_ppt(outline, output_path)
            
            # 验证生成的文件
            if not os.path.exists(output_path):
                raise Exception("PPT文件创建失败")
            
            file_size = os.path.getsize(output_path)
            if file_size < 1000:
                raise Exception("生成的PPT文件过小，可能生成失败")

            return output_path
            
        except Exception as e:
            log_step(f"PPT生成失败: {str(e)}")
            raise

    def generate_with_logging(self, content: str, pages: int = None) -> str:
        """生成PPT并记录过程"""
        if pages is None:
            pages = 3  # 默认3页
        print(f"\n\033[94m[工具调用]\033[0m 开始生成{pages}页PPT，主题：{content}")
        try:
            result = self.generate(content, pages)
            print(f"{result}")
            return result
        except Exception as e:
            print(f"\033[91m[工具错误]\033[0m PPT生成失败：{str(e)}")
            raise


if __name__ == "__main__":
    # 使用示例
    generator = PPTGenerator()

    # 测试输入
    input_data = """请为我生成一个主题为"2025年的创业机遇"的PPT，需要3页。要求：
    1. 内容要全面且具有前瞻性
    2. 包含最新的市场数据和趋势
    3. 重点分析未来的创业方向
    4. 配图要专业且美观
    """

    print(f"\n\033[94m[用户输入]\033[0m\n{input_data}")

    # 执行测试
    result = generator.agent_executor.invoke({
        "input": input_data
    })

    # 提取文件路径
    output = result['output']
    path_start = output.find('output/ppt')
    path_end = output.find('.pptx') + 5
    if path_start != -1 and path_end != -1:
        file_path = output[path_start:path_end]
        print(f"\n\033[92m[执行结果]\033[0m")
        print(f"PPT已生成并保存到：{file_path}")
    else:
        print(f"\n\033[92m[执行结果]\033[0m")
        print(output)
